Redis主从复制

两种方式

  • 命令实现:
  • image-20190805200946298

image-20190805201007180

image-20190805201041289


image-20190805201054605



image-20190805203557445


image-20190805203658640

image-20190805204030842

redis系列:主从复制

1 简介

这篇文章主要讲述Redis的主从复制功能。会依次从环境搭建、功能测试和原理分析几个方面进行介绍。

2 准备工作

服务器架构图如下

image-20190805211753888

启动主服务器101,使用info replication命令查看状态,可以看到role为master(也就是角色为主主服务器),connected_salaves的值为0(从服务器数量为0)

image-20190805211808251

接下来用修改配置文件的方式将102机器加入的主从复制当中

然后再用命令的方式同样将103机器加入的主从复制当中。

2.1 用修改配置文件的方式将102机器加入到主从

ip地址为192.168.17.102的机器的Redis配置文件增加slaveof 192.168.17.101 6379
启动102的redis,状态如下

image-20190805211821924

可以看到role变为slave(角色为从服务器),master_host(主服务器IP地址)为192.168.17.101,master_port(主服务器端口)为6379。
此时101主服务器的主从状态如下,可以看到connected_salaves的值变为1,以及增加了一行slave0(从服务器的状态)

image-20190805211831926

2.2 用命令的方式将103机器加入到主从

未执行slaveof命令的主从状态如下

image-20190805211841628

开始执行slaveof命令

1
2
3
192.168.17.103:6379> slaveof 192.168.17.101 6379
OK
复制代码

再次查看状态,可以看到角色已经变成从服务器

image-20190805211852175

现在再来看看主服务器的状态,可以看到从服务器数量变成2,又多了一条从服务器的信息

image-20190805211904051

到这里主从环境就搭好了,现在来测试一波

2.3 测试

现在主服务器101输入命令

1
2
3
192.168.17.101:6379> set 101 101
OK
复制代码

然后在从服务器102上查看所有的键,发现有键101,接着设置键102

1
2
3
4
5
6
7
192.168.17.102:6379> keys *
1) "101"
192.168.17.102:6379> get 101
"101"
192.168.17.102:6379> set 102 102
(error) READONLY You can't write against a read only slave.
复制代码

发现出现错误(error) READONLY You can't write against a read only slave. 后面在讲述出错原因

现在在从服务器103上查看所有的键,发现也有101

1
2
3
192.168.17.103:6379> keys *
1) "101"
复制代码

再向主服务器101输入命令

1
2
3
192.168.17.101:6379> set ip ip
OK
复制代码

然后到从服务器103上查看所有的键

1
2
3
4
192.168.17.103:6379> keys *
1) "101"
2) "ip"
复制代码

可以看到多了一个键,说明主服务的数据同步到了从服务器上,操作过程看下图

img

2.4 其他

2.4.1 (error) READONLY You can’t write against a read only slave.

出现错误(error) READONLY You can't write against a read only slave. 是因为 从节点默认是只读的,如需修改可以再配置文件中修改下面这个属性

1
2
slave-read-only yes
复制代码

2.4.2 主服务器设置密码

当主服务设置密码时,配置文件需要增加如需参数

1
2
masterauth <master-password>
复制代码

3 实现原理

当我在从服务器103上输入slaveof命令时,出现如下日志

image-20190805211926657

总的来说主从复制功能的详细步骤可以分为7个步骤:

  1. 设置主服务器的地址和端口
  2. 建立套接字连接
  3. 发送PING命令
  4. 身份验证
  5. 发送端口信息
  6. 同步
  7. 命令传播

接下来分别叙述每个步骤

3.1设置主服务器的地址和端口

主从复制的第一步就是设置主服务器的地址和端口,当输入slaveof命令或者在配置文件中配置信息时,从服务器会将主服务器的ip地址和端口号保存到服务器状态的属性里面。

3.2 建立套接字连接

在slaveof命令执行之后,从服务器会根据设置的ip和端口,向主服务器简历socket连接。

3.3 发送PING命令

socket连接成功后,从服务器会发送一PING命令给主服务器。

这时候PING命令可以检查socket的读写状态是否正常,还可以检查主服务器能否正常处理命令请求。

从服务器在发送PING命令时可能遇上的情况如下图

图片来自Redis设计与实现

3.4 身份验证

从服务器收到主服务器的PONG回复后,会检查从服务器是否设置masterauth,设置则进行身份验证,未设置则跳过该步骤。从服务器在身份验证时可能遇上的情况如下

图片来自Redis设计与实现

3.5 发送端口信息

身份验证通过后,从服务器会向主服务器发送自己的监听端口号。主服务器收到之后会将端口号记录到从服务器对应的状态属性中。在主服务器调用info replication可以看到从服务器的port,如下

image-20190805211938131

3.6 同步

发送端口信息之后,从服务器会向主服务器发送PSYNC命令,执行同步操作,并将自己的数据库同步至主服务器数据库当前的状态。

同步这块内容会在后面详细描述

3.7 命令传播

当完成同步操作之后,主从服务器便会进入命令传播阶段。这时候主从服务器的数据是一致的,当主服务器有新的写命令时,会将改命令发送给从服务器,从服务器接收命令并执行便可以保证与主服务器的数据保持一致。
那么Redis是如何保证主从服务器一致处于连接状态以及命令是否丢失?
答:命令传播阶段,从服务器会利用心跳检测机制定时的向主服务发送消息。
从服务器发送的命令如下

1
2
REPLCONF ACK <replication_offset>
复制代码

replication_offset表示从服务器当前的复制偏移量
接下来看看心跳机制

3.7.1 心跳检测机制

心跳检测机制的作用有三个:

  1. 检查主从服务器的网络连接状态
  2. 辅助实现min-slaves选项
  3. 检测命令丢失

3.7.1.1 检查主从服务器的网络连接状态

主服务器信息中可以看到所属的从服务器的连接信息,state表示从服务器状态,offset表示复制偏移量,lag表示延迟值(几秒之前有过心跳检测机制)

image-20190805211951647

3.7.1.2 辅助实现min-slaves选项

Redis.conf配置文件中有下方两个参数

1
2
3
4
5
6
# 未达到下面两个条件时,写操作就不会被执行
# 最少包含的从服务器
# min-slaves-to-write 3
# 延迟值
# min-slaves-max-lag 10
复制代码

如果将两个参数的注释取消,那么如果从服务器的数量少于3个,或者三个从服务器的延迟(lag)大于等于10秒时,主服务器都会拒绝执行写命令。

3.7.1.3 检测命令丢失

在从服务器的连接信息中可以看到复制偏移量,如果此时主服务器的复制偏移量与从服务器的复制偏移量不一致时,主服务器会补发缺失的数据。

4 同步原理

同步分为全量重同步和部分重同步。那么是什么决定采取全量重同步还是部分重同步操作?

image-20190805212004746

4.1 全量重同步

全量重同步的步骤如下

  1. 主节点收到从服务器的全量重同步请求时,主服务器便开始执行bgsave命令,同时用一个缓冲区记录从现在开始执行的所有写命令。
  2. 当主服务器的bgsave命令执行完毕后,会将生成的RDB文件发送给从服务器。从服务器接收到RDB文件时,会将数据文件保存到硬盘,然后加载到内存中。
  3. 主服务器将缓冲区所有缓存的命令发送到从服务器,从服务器接收并执行这些命令,将从服务器同步至主服务器相同的状态。

4.2 部分重同步

要想了解部分重同步的步骤,需要先了解部分重同步所需要的几个属性

  1. 复制偏移量
  2. 复制缓冲区
  3. 运行ID

4.2.1 复制偏移量

从主服务器的复制信息可以看到从服务器slave0和slave1都有一个参数offset,这个参数就是从服务器的复制偏移量。master_repl_offset这个参数就是主服务器的偏移量。如下图

image-20190805212015720

主服务器的复制偏移量保存向从服务器发送过的字节数据。
从服务器的复制偏移量保存着从主服务器接收的字节数据。
通过对比主服务器和从服务器的复制偏移量就可以知道命令是否丢失,丢失则补发复制偏移量相差的字节命令。
那么这些字节数据是存放在哪里的呢?

4.2.2 复制缓冲区

这些字节数据都是存放在主服务器的复制缓冲区里的。复制缓冲区是一个固定长度(fixed-size)先进先出(FIFO)的队列,默认大小为1MB。默认大小可以对下方的参数进行修改

1
2
# repl-backlog-size 1mb
复制代码

那么复制缓冲区的数据是什么时候加入进去的呢?

答:在命令传播阶段,主节点除了将写命令发送给从节点,还会发送一份给复制积压缓冲区。

image-20190805212026304

复制缓冲区里面会保存着一部分最传播的写命令和每个字节相应的复制偏移量。

由于复制缓冲区的大小是有限制的,所以保存的数据也是有限制的。如果从服务器与主服务器的复制偏移量相差的数据大于复制缓冲去存储的数据时,同样不会执行部分重同步。

举个例子,主服务器的复制偏移量为20000、缓冲区能保存的数据只有5000,从服务器的复制偏移量为10000。这时从服务器与主服务器复制偏移量10000,而缓冲区只有5000,那么还是会执行全量重同步。如果相差的复制偏移量小于5000,才会执行部分重同步。

4.2.3 运行ID

每个Redis服务器启动时,都会有自动生成自己的运行ID。
当从服务器对主服务器进行初次复制时,主服务器会发送自己的运行ID给从服务器。
当从服务器断线重连时,会将之前主服务器的运行ID发送给当前连接的主服务器。这时候会出现下面两种情况

  1. 运行ID和主服务器一致,主服务器可以尝试执行部分重同步操作。
  2. 运行ID和主服务器不一致,说明之前连接的主服务器与这次连接不同,开始执行全量重同步操作。

5 相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
################################# REPLICATION #################################

# slaveof <主服务器ip> <主服务器端口>
# slaveof <masterip> <masterport>

# masterauth <主服务器Redis密码>
# masterauth <master-password>

# 当slave丢失master或者同步正在进行时,如果发生对slave的服务请求
# yes则slave依然正常提供服务
# no则slave返回client错误:"SYNC with master in progress"
slave-serve-stale-data yes

# 指定slave是否只读
slave-read-only yes

# 无硬盘复制功能
repl-diskless-sync no

# 无硬盘复制功能间隔时间
repl-diskless-sync-delay 5

# 从服务器发送PING命令给主服务器的周期
# repl-ping-slave-period 10

# 超时时间
# repl-timeout 60

# 是否禁用socket的NO_DELAY选项
repl-disable-tcp-nodelay no

# 设置主从复制容量大小,这个backlog 是一个用来在 slaves 被断开连接时存放 slave 数据的 buffer
# repl-backlog-size 1mb

# master 不再连接 slave时backlog的存活时间。
# repl-backlog-ttl 3600

# slave的优先级
slave-priority 100

# 未达到下面两个条件时,写操作就不会被执行
# 最少包含的从服务器
# min-slaves-to-write 3
# 延迟值
# min-slaves-max-lag 10
复制代码

结语

主从的配置文件:[https://github.com/rainbowda/learnWay/tree/master/learnRedis/replication,有需要可以下载。

Redis的主从复制功能就介绍到这里了。虽然说主从解决了读写分离,读数据的负载均衡,但是一旦某个节点出现故障,不能自动回复,主从切换等功能。所以就有了哨兵的功能。

深入学习Redis(3):主从复制

前言

在前面的两篇文章中,分别介绍了Redis的内存模型Redis的持久化

在Redis的持久化中曾提到,Redis高可用的方案包括持久化、主从复制(及读写分离)、哨兵和集群。其中持久化侧重解决的是Redis数据的单机备份问题(从内存到硬盘的备份);而主从复制则侧重解决数据的多机热备。此外,主从复制还可以实现负载均衡和故障恢复。

这篇文章中,将详细介绍Redis主从复制的方方面面,包括:如何使用主从复制、主从复制的原理(重点是全量复制和部分复制、以及心跳机制)、实际应用中需要注意的问题(如数据不一致问题、复制超时问题、复制缓冲区溢出问题)、主从复制相关的配置(重点是repl-timeout、client-output-buffer-limit slave)等。

系列文章

深入学习Redis(1):Redis内存模型

深入学习Redis(2):持久化

深入学习Redis(3):主从复制

深入学习Redis(4):哨兵

深入学习Redis(5):集群

目录

一、主从复制概述

二、如何使用主从复制

1. 建立复制

2. 实例

3. 断开复制

三、主从复制的实现原理

1. 连接建立阶段

2. 数据同步阶段

3. 命令传播阶段

四、【数据同步阶段】全量复制和部分复制

1. 全量复制

2. 部分复制

3. psync命令的执行

4. 部分复制演示

五、【命令传播阶段】心跳机制

1. 主->从:PING

2. 从->主:REPLCONF ACK

六、应用中的问题

1. 读写分离及其中的问题

2. 复制超时问题

3. 复制中断问题

4. 各场景下复制的选择及优化技巧

5. 复制相关的配置

6. 单机内存大小限制

7. info Replication

七、总结

一、主从复制概述

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

主从复制的作用

主从复制的作用主要包括:

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  4. 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

二、如何使用主从复制

为了更直观的理解主从复制,在介绍其内部原理之前,先说明我们需要如何操作才能开启主从复制。

1. 建立复制

需要注意,主从复制的开启,完全是在从节点发起的;不需要我们在主节点做任何事情。

从节点开启主从复制,有3种方式:

(1)配置文件

在从服务器的配置文件中加入:slaveof

(2)启动命令

redis-server启动命令后加入 –slaveof

(3)客户端命令

Redis服务器启动后,直接通过客户端执行命令:slaveof ,则该Redis实例成为从节点。

上述3种方式是等效的,下面以客户端命令的方式为例,看一下当执行了slaveof后,Redis主节点和从节点的变化。

2. 实例

准备工作:启动两个节点

方便起见,实验所使用的主从节点是在一台机器上的不同Redis实例,其中主节点监听6379端口,从节点监听6380端口;从节点监听的端口号可以在配置文件中修改:

image-20190805212038956

启动后可以看到:

image-20190805212045985

两个Redis节点启动后(分别称为6379节点和6380节点),默认都是主节点。

建立复制

此时在6380节点执行slaveof命令,使之变为从节点:

image-20190805212053324

观察效果

下面验证一下,在主从复制建立后,主节点的数据会复制到从节点中。

(1)首先在从节点查询一个不存在的key:

img

(2)然后在主节点中增加这个key:

image-20190805212101976

(3)此时在从节点中再次查询这个key,会发现主节点的操作已经同步至从节点:

img

(4)然后在主节点删除这个key:

image-20190805212109135

(5)此时在从节点中再次查询这个key,会发现主节点的操作已经同步至从节点:

image-20190805212116246

3. 断开复制

通过slaveof 命令建立主从复制关系以后,可以通过slaveof no one断开。需要注意的是,从节点断开复制后,不会删除已有的数据,只是不再接受主节点新的数据变化。

从节点执行slaveof no one后,打印日志如下所示;可以看出断开复制后,从节点又变回为主节点。

image-20190805212122087

主节点打印日志如下:

image-20190805212126798

三、主从复制的实现原理

上面一节中,介绍了如何操作可以建立主从关系;本小节将介绍主从复制的实现原理。

主从复制过程大体可以分为3个阶段:连接建立阶段(即准备阶段)、数据同步阶段、命令传播阶段;下面分别进行介绍。

1. 连接建立阶段

该阶段的主要作用是在主从节点之间建立连接,为数据同步做好准备。

步骤1:保存主节点信息

从节点服务器内部维护了两个字段,即masterhost和masterport字段,用于存储主节点的ip和port信息。

需要注意的是,slaveof**是异步命令,从节点完成主节点ip和port的保存后,向发送slaveof命令的客户端直接返回OK,实际的复制操作在这之后才开始进行。**

这个过程中,可以看到从节点打印日志如下:

image-20190805212132686

步骤2:建立socket连接

从节点每秒1次调用复制定时函数replicationCron(),如果发现了有主节点可以连接,便会根据主节点的ip和port,创建socket连接。如果连接成功,则:

从节点:为该socket建立一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB文件、接收命令传播等。

主节点:接收到从节点的socket连接后(即accept之后),为该socket创建相应的客户端状态,并将从节点看做是连接到主节点的一个客户端,后面的步骤会以从节点向主节点发送命令请求的形式来进行。

这个过程中,从节点打印日志如下:

image-20190805212137946

步骤3:发送ping命令

从节点成为主节点的客户端之后,发送ping命令进行首次请求,目的是:检查socket连接是否可用,以及主节点当前是否能够处理请求。

从节点发送ping命令后,可能出现3种情况:

(1)返回pong:说明socket连接正常,且主节点当前可以处理请求,复制过程继续。

(2)超时:一定时间后从节点仍未收到主节点的回复,说明socket连接不可用,则从节点断开socket连接,并重连。

(3)返回pong以外的结果:如果主节点返回其他结果,如正在处理超时运行的脚本,说明主节点当前无法处理命令,则从节点断开socket连接,并重连。

在主节点返回pong情况下,从节点打印日志如下:

image-20190805212143066

步骤4:身份验证

如果从节点中设置了masterauth选项,则从节点需要向主节点进行身份验证;没有设置该选项,则不需要验证。从节点进行身份验证是通过向主节点发送auth命令进行的,auth命令的参数即为配置文件中的masterauth的值。

如果主节点设置密码的状态,与从节点masterauth的状态一致(一致是指都存在,且密码相同,或者都不存在),则身份验证通过,复制过程继续;如果不一致,则从节点断开socket连接,并重连。

步骤5:发送从节点端口信息

身份验证之后,从节点会向主节点发送其监听的端口号(前述例子中为6380),主节点将该信息保存到该从节点对应的客户端的slave_listening_port字段中;该端口信息除了在主节点中执行info Replication时显示以外,没有其他作用。

2. 数据同步阶段

主从节点之间的连接建立以后,便可以开始进行数据同步,该阶段可以理解为从节点数据的初始化。具体执行的方式是:从节点向主节点发送psync命令(Redis2.8以前是sync命令),开始同步。

数据同步阶段是主从复制最核心的阶段,根据主从节点当前状态的不同,可以分为全量复制和部分复制,下面会有一章专门讲解这两种复制方式以及psync命令的执行过程,这里不再详述。

需要注意的是,在数据同步阶段之前,从节点是主节点的客户端,主节点不是从节点的客户端;而到了这一阶段及以后,主从节点互为客户端。原因在于:在此之前,主节点只需要响应从节点的请求即可,不需要主动发请求,而在数据同步阶段和后面的命令传播阶段,主节点需要主动向从节点发送请求(如推送缓冲区中的写命令),才能完成复制。

3. 命令传播阶段

数据同步阶段完成后,主从节点进入命令传播阶段;在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。

在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。由于心跳机制的原理涉及部分复制,因此将在介绍了部分复制的相关内容后单独介绍该心跳机制。

延迟与不一致

需要注意的是,命令传播是异步的过程,即主节点发送写命令后并不会等待从节点的回复;因此实际上主从节点之间很难保持实时的一致性,延迟在所难免。数据不一致的程度,与主从节点之间的网络状况、主节点写命令的执行频率、以及主节点中的repl-disable-tcp-nodelay配置等有关。

repl-disable-tcp-nodelay no:该配置作用于命令传播阶段,控制主节点是否禁止与从节点的TCP_NODELAY;默认no,即不禁止TCP_NODELAY。当设置为yes时,TCP会对包进行合并从而减少带宽,但是发送的频率会降低,从节点数据延迟增加,一致性变差;具体发送频率与Linux内核的配置有关,默认配置为40ms。当设置为no时,TCP会立马将主节点的数据发送给从节点,带宽增加但延迟变小。

一般来说,只有当应用对Redis数据不一致的容忍度较高,且主从节点之间网络状况不好时,才会设置为yes;多数情况使用默认值no。

四、【数据同步阶段】全量复制和部分复制

在Redis2.8以前,从节点向主节点发送sync命令请求同步数据,此时的同步方式是全量复制;在Redis2.8及以后,从节点可以发送psync命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制或部分复制。后文介绍以Redis2.8及以后版本为例。

  1. 全量复制:用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。
  2. 部分复制:用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制。

1. 全量复制

Redis通过psync命令进行全量复制的过程如下:

(1)从节点判断无法进行部分复制,向主节点发送全量复制的请求;或从节点发送部分复制的请求,但主节点判断无法进行全量复制;具体判断过程需要在讲述了部分复制原理后再介绍。

(2)主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令

(3)主节点的bgsave执行完成后,将RDB文件发送给从节点;从节点首先清除自己的旧数据,然后载入接收的**RDB**文件,将数据库状态更新至主节点执行bgsave时的数据库状态

(4)主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态

(5)如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态

下面是执行全量复制时,主从节点打印的日志;可以看出日志内容与上述步骤是完全对应的。

主节点的打印日志如下:

image-20190805212150939

从节点打印日志如下图所示:

image-20190805212155008

其中,有几点需要注意:从节点接收了来自主节点的89260个字节的数据;从节点在载入主节点的数据之前要先将老数据清除;从节点在同步完数据后,调用了bgrewriteaof。

通过全量复制的过程可以看出,全量复制是非常重型的操作:

(1)主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘IO的;关于bgsave的性能问题,可以参考 深入学习Redis(2):持久化

(2)主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗

(3)从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行bgrewriteaof,也会带来额外的消耗

2. 部分复制

由于全量复制在主节点数据量较大时效率太低,因此Redis2.8开始提供部分复制,用于处理网络中断时的数据同步。

部分复制的实现,依赖于三个重要的概念:

(1)复制偏移量

主节点和从节点分别维护一个复制偏移量(offset),代表的是主节点向从节点传递的字节数;主节点每次向从节点传播N个字节数据时,主节点的offset增加N;从节点每次收到主节点传来的N个字节数据时,从节点的offset增加N。

offset用于判断主从节点的数据库状态是否一致:如果二者offset相同,则一致;如果offset不同,则不一致,此时可以根据两个offset找出从节点缺少的那部分数据。例如,如果主节点的offset是1000,而从节点的offset是500,那么部分复制就需要将offset为501-1000的数据传递给从节点。而offset为501-1000的数据存储的位置,就是下面要介绍的复制积压缓冲区。

(2)复制积压缓冲区

复制积压缓冲区是由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小1MB;当主节点开始有从节点时创建,其作用是备份主节点最近发送给从节点的数据。注意,无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。

在命令传播阶段,主节点除了将写命令发送给从节点,还会发送一份给复制积压缓冲区,作为写命令的备份;除了存储写命令,复制积压缓冲区中还存储了其中的每个字节对应的复制偏移量(offset)。由于复制积压缓冲区定长且是先进先出,所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。

由于该缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。反过来说,为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size);例如如果网络中断的平均时间是60s,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为100KB,则复制积压缓冲区的平均需求为6MB,保险起见,可以设置为12MB,来保证绝大多数断线情况都可以使用部分复制。

从节点将offset发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制:

  • 如果offset偏移量之后的数据,仍然都在复制积压缓冲区里,则执行部分复制;
  • 如果offset偏移量之后的数据已不在复制积压缓冲区中(数据已被挤出),则执行全量复制。

(3)服务器运行ID(runid)

每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成;runid用来唯一识别一个Redis节点。通过info Server命令,可以查看节点的runid:

image-20190805212218251

主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制:

  • 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
  • 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。

3. psync命令的执行

在了解了复制偏移量、复制积压缓冲区、节点运行id之后,本节将介绍psync命令的参数和返回值,从而说明psync命令执行过程中,主从节点是如何确定使用全量复制还是部分复制的。

psync命令的执行过程可以参见下图(图片来源:《Redis设计与实现》):

![imagimage-20190805212212118

(1)首先,从节点根据当前状态,决定如何调用psync命令:

  • 如果从节点之前未执行过slaveof或最近执行了slaveof no one,则从节点发送命令为psync ? -1,向主节点请求全量复制;
  • 如果从节点之前执行了slaveof,则发送命令为psync ,其中runid为上次复制的主节点的runid,offset为上次复制截止时从节点保存的复制偏移量。

(2)主节点根据收到的psync命令,及当前服务器状态,决定执行全量复制还是部分复制:

  • 如果主节点版本低于Redis2.8,则返回-ERR回复,此时从节点重新发送sync命令执行全量复制;
  • 如果主节点版本够新,且runid与从节点发送的runid相同,且从节点发送的offset之后的数据在复制积压缓冲区中都存在,则回复+CONTINUE,表示将进行部分复制,从节点等待主节点发送其缺少的数据即可;
  • 如果主节点版本够新,但是runid与从节点发送的runid不同,或从节点发送的offset之后的数据已不在复制积压缓冲区中(在队列中被挤出了),则回复+FULLRESYNC ,表示要进行全量复制,其中runid表示主节点当前的runid,offset表示主节点当前的offset,从节点保存这两个值,以备使用。

4. 部分复制演示

在下面的演示中,网络中断几分钟后恢复,断开连接的主从节点进行了部分复制;为了便于模拟网络中断,本例中的主从节点在局域网中的两台机器上。

网络中断

网络中断一段时间后,主节点和从节点都会发现失去了与对方的连接(关于主从节点对超时的判断机制,后面会有说明);此后,从节点便开始执行对主节点的重连,由于此时网络还没有恢复,重连失败,从节点会一直尝试重连。

主节点日志如下:

image-20190805212228333

从节点日志如下:

image-20190805212224168

网络恢复

网络恢复后,从节点连接主节点成功,并请求进行部分复制,主节点接收请求后,二者进行部分复制以同步数据。

主节点日志如下:

image-20190805212238819

从节点日志如下:

image-20190805212233961

五、【命令传播阶段】心跳机制

在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。

1.主->从:PING

每隔指定的时间,主节点会向从节点发送**PING**命令,这个PING命令的作用,主要是为了让从节点进行超时判断。

PING发送的频率由repl-ping-slave-period参数控制,单位是秒,默认值是10s。

关于该PING命令究竟是由主节点发给从节点,还是相反,有一些争议;因为在Redis的官方文档中,对该参数的注释中说明是从节点向主节点发送PING命令,如下图所示:

img

但是根据该参数的名称(含有ping-slave),以及代码实现,我认为该PING命令是主节点发给从节点的。相关代码如下:

img

2. 从->主:REPLCONF ACK

在命令传播阶段,从节点会向主节点发送**REPLCONF ACK**命令,频率是每秒1次;命令格式为:REPLCONF ACK {offset},其中offset指从节点保存的复制偏移量。REPLCONF ACK命令的作用包括:

(1)实时监测主从节点网络状态:该命令会被主节点用于复制超时的判断。此外,在主节点中使用info Replication,可以看到其从节点的状态中的lag值,代表的是主节点上次收到该REPLCONF ACK命令的时间间隔,在正常情况下,该值应该是0或1,如下图所示:

img

(2)检测命令丢失:从节点发送了自身的offset,主节点会与自己的offset对比,如果从节点数据缺失(如网络丢包),主节点会推送缺失的数据(这里也会利用复制积压缓冲区)。注意,**offset**和复制积压缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的。

(3)辅助保证从节点的数量和延迟:Redis主节点中使用min-slaves-to-write和min-slaves-max-lag参数,来保证主节点在不安全的情况下不会执行写命令;所谓不安全,是指从节点数量太少,或延迟过高。例如min-slaves-to-write和min-slaves-max-lag分别是3和10,含义是如果从节点数量小于3个,或所有从节点的延迟值都大于10s,则主节点拒绝执行写命令。而这里从节点延迟值的获取,就是通过主节点接收到REPLCONF ACK命令的时间来判断的,即前面所说的info Replication中的lag值。

六、应用中的问题

1. 读写分离及其中的问题

在主从复制基础上实现的读写分离,可以实现Redis的读负载均衡:由主节点提供写服务,由一个或多个从节点提供读服务(多个从节点既可以提高数据冗余程度,也可以最大化读负载能力);在读负载较大的应用场景下,可以大大提高Redis服务器的并发量。下面介绍在使用Redis读写分离时,需要注意的问题。

(1)延迟与不一致问题

前面已经讲到,由于主从复制的命令传播是异步的,延迟与数据的不一致不可避免。如果应用对数据不一致的接受程度程度较低,可能的优化措施包括:优化主从节点之间的网络环境(如在同机房部署);监控主从节点延迟(通过offset)判断,如果从节点延迟过大,通知应用不再通过该从节点读取数据;使用集群同时扩展写负载和读负载等。

在命令传播阶段以外的其他情况下,从节点的数据不一致可能更加严重,例如连接在数据同步阶段,或从节点失去与主节点的连接时等。从节点的slave-serve-stale-data参数便与此有关:它控制这种情况下从节点的表现;如果为yes(默认值),则从节点仍能够响应客户端的命令,如果为no,则从节点只能响应info、slaveof等少数命令。该参数的设置与应用对数据一致性的要求有关;如果对数据一致性要求很高,则应设置为no。

(2)数据过期问题

在单机版Redis中,存在两种删除策略:

  • 惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。
  • 定期删除:服务器执行定时任务删除过期数据,但是考虑到内存和CPU的折中(删除会释放内存,但是频繁的删除操作对CPU不友好),该删除的频率和执行时间都受到了限制。

在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过Redis从节点读取数据时,很容易读取到已经过期的数据。

Redis 3.2中,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则不返回给客户端;将Redis升级到3.2可以解决数据过期问题。

(3)故障切换问题

在没有使用哨兵的读写分离场景下,应用针对读和写分别连接不同的Redis节点;当主节点或从节点出现问题而发生更改时,需要及时修改应用程序读写Redis数据的连接;连接的切换可以手动进行,或者自己写监控程序进行切换,但前者响应慢、容易出错,后者实现复杂,成本都不算低。

(4)总结

在使用读写分离之前,可以考虑其他方法增加Redis的读负载能力:如尽量优化主节点(减少慢查询、减少持久化等其他情况带来的阻塞等)提高负载能力;使用Redis集群同时提高读负载能力和写负载能力等。如果使用读写分离,可以使用哨兵,使主从节点的故障切换尽可能自动化,并减少对应用程序的侵入。

2. 复制超时问题

主从节点复制超时是导致复制中断的最重要的原因之一,本小节单独说明超时问题,下一小节说明其他会导致复制中断的问题。

超时判断意义

在复制连接建立过程中及之后,主从节点都有机制判断连接是否超时,其意义在于:

(1)如果主节点判断连接超时,其会释放相应从节点的连接,从而释放各种资源,否则无效的从节点仍会占用主节点的各种资源(输出缓冲区、带宽、连接等);此外连接超时的判断可以让主节点更准确的知道当前有效从节点的个数,有助于保证数据安全(配合前面讲到的min-slaves-to-write等参数)。

(2)如果从节点判断连接超时,则可以及时重新建立连接,避免与主节点数据长期的不一致。

判断机制

主从复制超时判断的核心,在于repl-timeout参数,该参数规定了超时时间的阈值(默认60s),对于主节点和从节点同时有效;主从节点触发超时的条件分别如下:

(1)主节点:每秒1次调用复制定时函数replicationCron(),在其中判断当前时间距离上次收到各个从节点REPLCONF ACK的时间,是否超过了repl-timeout值,如果超过了则释放相应从节点的连接。

(2)从节点:从节点对超时的判断同样是在复制定时函数中判断,基本逻辑是:

  • 如果当前处于连接建立阶段,且距离上次收到主节点的信息的时间已超过repl-timeout,则释放与主节点的连接;
  • 如果当前处于数据同步阶段,且收到主节点的RDB文件的时间超时,则停止数据同步,释放连接;
  • 如果当前处于命令传播阶段,且距离上次收到主节点的PING命令或数据的时间已超过repl-timeout值,则释放与主节点的连接。

主从节点判断连接超时的相关源代码如下:

1
`/* Replication cron function, called 1 time per second. */``void` `replicationCron(``void``) {``    ``static` `long` `long` `replication_cron_loops = 0;` `    ``/* Non blocking connection timeout? */``    ``if` `(server.masterhost &&``        ``(server.repl_state == REDIS_REPL_CONNECTING ||``         ``slaveIsInHandshakeState()) &&``         ``(``time``(NULL)-server.repl_transfer_lastio) > server.repl_timeout)``    ``{``        ``redisLog(REDIS_WARNING,``"Timeout connecting to the MASTER..."``);``        ``undoConnectWithMaster();``    ``}` `    ``/* Bulk transfer I/O timeout? */``    ``if` `(server.masterhost && server.repl_state == REDIS_REPL_TRANSFER &&``        ``(``time``(NULL)-server.repl_transfer_lastio) > server.repl_timeout)``    ``{``        ``redisLog(REDIS_WARNING,``"Timeout receiving bulk data from MASTER... If the problem persists try to set the 'repl-timeout' parameter in redis.conf to a larger value."``);``        ``replicationAbortSyncTransfer();``    ``}` `    ``/* Timed out master when we are an already connected slave? */``    ``if` `(server.masterhost && server.repl_state == REDIS_REPL_CONNECTED &&``        ``(``time``(NULL)-server.master->lastinteraction) > server.repl_timeout)``    ``{``        ``redisLog(REDIS_WARNING,``"MASTER timeout: no data nor PING received..."``);``        ``freeClient(server.master);``    ``}` `    ``//此处省略无关代码……` `    ``/* Disconnect timedout slaves. */``    ``if` `(listLength(server.slaves)) {``        ``listIter li;``        ``listNode *ln;``        ``listRewind(server.slaves,&li);``        ``while``((ln = listNext(&li))) {``            ``redisClient *slave = ln->value;``            ``if` `(slave->replstate != REDIS_REPL_ONLINE) ``continue``;``            ``if` `(slave->flags & REDIS_PRE_PSYNC) ``continue``;``            ``if` `((server.unixtime - slave->repl_ack_time) > server.repl_timeout)``            ``{``                ``redisLog(REDIS_WARNING, ``"Disconnecting timedout slave: %s"``,``                    ``replicationGetSlaveName(slave));``                ``freeClient(slave);``            ``}``        ``}``    ``}` `    ``//此处省略无关代码……` `}`

  

需要注意的坑

下面介绍与复制阶段连接超时有关的一些实际问题:

(1)数据同步阶段:在主从节点进行全量复制bgsave时,主节点需要首先fork子进程将当前数据保存到RDB文件中,然后再将RDB文件通过网络传输到从节点。如果RDB文件过大,主节点在fork子进程+保存RDB文件时耗时过多,可能会导致从节点长时间收不到数据而触发超时;此时从节点会重连主节点,然后再次全量复制,再次超时,再次重连……这是个悲伤的循环。为了避免这种情况的发生,除了注意Redis单机数据量不要过大,另一方面就是适当增大repl-timeout值,具体的大小可以根据bgsave耗时来调整。

(2)命令传播阶段:如前所述,在该阶段主节点会向从节点发送PING命令,频率由repl-ping-slave-period控制;该参数应明显小于repl-timeout值(后者至少是前者的几倍)。否则,如果两个参数相等或接近,网络抖动导致个别PING命令丢失,此时恰巧主节点也没有向从节点发送数据,则从节点很容易判断超时。

(3)慢查询导致的阻塞:如果主节点或从节点执行了一些慢查询(如keys *或者对大数据的hgetall等),导致服务器阻塞;阻塞期间无法响应复制连接中对方节点的请求,可能导致复制超时。

3. 复制中断问题

主从节点超时是复制中断的原因之一,除此之外,还有其他情况可能导致复制中断,其中最主要的是复制缓冲区溢出问题。

复制缓冲区溢出

前面曾提到过,在全量复制阶段,主节点会将执行的写命令放到复制缓冲区中,该缓冲区存放的数据包括了以下几个时间段内主节点执行的写命令:bgsave生成RDB文件、RDB文件由主节点发往从节点、从节点清空老数据并载入RDB文件中的数据。当主节点数据量较大,或者主从节点之间网络延迟较大时,可能导致该缓冲区的大小超过了限制,此时主节点会断开与从节点之间的连接;这种情况可能引起全量复制->复制缓冲区溢出导致连接中断->重连->全量复制->复制缓冲区溢出导致连接中断……的循环。

复制缓冲区的大小由client-output-buffer-limit slave {hard limit} {soft limit} {soft seconds}配置,默认值为client-output-buffer-limit slave 256MB 64MB 60,其含义是:如果buffer大于256MB,或者连续60s大于64MB,则主节点会断开与该从节点的连接。该参数是可以通过config set命令动态配置的(即不重启Redis也可以生效)。

当复制缓冲区溢出时,主节点打印日志如下所示:

img

需要注意的是,复制缓冲区是客户端输出缓冲区的一种,主节点会为每一个从节点分别分配复制缓冲区;而复制积压缓冲区则是一个主节点只有一个,无论它有多少个从节点。

4. 各场景下复制的选择及优化技巧

在介绍了Redis复制的种种细节之后,现在我们可以来总结一下,在下面常见的场景中,何时使用部分复制,以及需要注意哪些问题。

(1)第一次建立复制

此时全量复制不可避免,但仍有几点需要注意:如果主节点的数据量较大,应该尽量避开流量的高峰期,避免造成阻塞;如果有多个从节点需要建立对主节点的复制,可以考虑将几个从节点错开,避免主节点带宽占用过大。此外,如果从节点过多,也可以调整主从复制的拓扑结构,由一主多从结构变为树状结构(中间的节点既是其主节点的从节点,也是其从节点的主节点);但使用树状结构应该谨慎:虽然主节点的直接从节点减少,降低了主节点的负担,但是多层从节点的延迟增大,数据一致性变差;且结构复杂,维护相当困难。

(2)主节点重启

主节点重启可以分为两种情况来讨论,一种是故障导致宕机,另一种则是有计划的重启。

主节点宕机

主节点宕机重启后,runid会发生变化,因此不能进行部分复制,只能全量复制。

实际上在主节点宕机的情况下,应进行故障转移处理,将其中的一个从节点升级为主节点,其他从节点从新的主节点进行复制;且故障转移应尽量的自动化,后面文章将要介绍的哨兵便可以进行自动的故障转移。

安全重启:debug reload

在一些场景下,可能希望对主节点进行重启,例如主节点内存碎片率过高,或者希望调整一些只能在启动时调整的参数。如果使用普通的手段重启主节点,会使得runid发生变化,可能导致不必要的全量复制。

为了解决这个问题,Redis提供了debug reload的重启方式:重启后,主节点的**runid和offset都不受影响,**避免了全量复制。

如下图所示,debug reload重启后runid和offset都未受影响:

img

但debug reload是一柄双刃剑:它会清空当前内存中的数据,重新从RDB文件中加载,这个过程会导致主节点的阻塞,因此也需要谨慎。

(3)从节点重启

从节点宕机重启后,其保存的主节点的runid会丢失,因此即使再次执行slaveof,也无法进行部分复制。

(4)网络中断

如果主从节点之间出现网络问题,造成短时间内网络中断,可以分为多种情况讨论。

第一种情况:网络问题时间极为短暂,只造成了短暂的丢包,主从节点都没有判定超时(未触发repl-timeout);此时只需要通过REPLCONF ACK来补充丢失的数据即可。

第二种情况:网络问题时间很长,主从节点判断超时(触发了repl-timeout),且丢失的数据过多,超过了复制积压缓冲区所能存储的范围;此时主从节点无法进行部分复制,只能进行全量复制。为了尽可能避免这种情况的发生,应该根据实际情况适当调整复制积压缓冲区的大小;此外及时发现并修复网络中断,也可以减少全量复制。

第三种情况:介于前述两种情况之间,主从节点判断超时,且丢失的数据仍然都在复制积压缓冲区中;此时主从节点可以进行部分复制。

5. 复制相关的配置

这一节总结一下与复制有关的配置,说明这些配置的作用、起作用的阶段,以及配置方法等;通过了解这些配置,一方面加深对Redis复制的了解,另一方面掌握这些配置的方法,可以优化Redis的使用,少走坑。

配置大致可以分为主节点相关配置、从节点相关配置以及与主从节点都有关的配置,下面分别说明。

(1)与主从节点都有关的配置

首先介绍最特殊的配置,它决定了该节点是主节点还是从节点:

1) slaveof :Redis启动时起作用;作用是建立复制关系,开启了该配置的Redis服务器在启动后成为从节点。该注释默认注释掉,即Redis服务器默认都是主节点。

2) repl-timeout 60:与各个阶段主从节点连接超时判断有关,见前面的介绍。

(2)主节点相关配置

1) repl-diskless-sync no:作用于全量复制阶段,控制主节点是否使用diskless复制(无盘复制)。所谓diskless复制,是指在全量复制时,主节点不再先把数据写入RDB文件,而是直接写入slave的socket中,整个过程中不涉及硬盘;diskless复制在磁盘IO很慢而网速很快时更有优势。需要注意的是,截至Redis3.0,diskless复制处于实验阶段,默认是关闭的。

2) repl-diskless-sync-delay 5:该配置作用于全量复制阶段,当主节点使用diskless复制时,该配置决定主节点向从节点发送之前停顿的时间,单位是秒;只有当diskless复制打开时有效,默认5s。之所以设置停顿时间,是基于以下两个考虑:(1)向slave的socket的传输一旦开始,新连接的slave只能等待当前数据传输结束,才能开始新的数据传输 (2)多个从节点有较大的概率在短时间内建立主从复制。

3) client-output-buffer-limit slave 256MB 64MB 60:与全量复制阶段主节点的缓冲区大小有关,见前面的介绍。

4) repl-disable-tcp-nodelay no:与命令传播阶段的延迟有关,见前面的介绍。

5) masterauth :与连接建立阶段的身份验证有关,见前面的介绍。

6) repl-ping-slave-period 10:与命令传播阶段主从节点的超时判断有关,见前面的介绍。

7) repl-backlog-size 1mb:复制积压缓冲区的大小,见前面的介绍。

8) repl-backlog-ttl 3600:当主节点没有从节点时,复制积压缓冲区保留的时间,这样当断开的从节点重新连进来时,可以进行全量复制;默认3600s。如果设置为0,则永远不会释放复制积压缓冲区。

9) min-slaves-to-write 3与min-slaves-max-lag 10:规定了主节点的最小从节点数目,及对应的最大延迟,见前面的介绍。

(3)从节点相关配置

1) slave-serve-stale-data yes:与从节点数据陈旧时是否响应客户端命令有关,见前面的介绍。

2) slave-read-only yes:从节点是否只读;默认是只读的。由于从节点开启写操作容易导致主从节点的数据不一致,因此该配置尽量不要修改。

6. 单机内存大小限制

深入学习Redis(2):持久化 一文中,讲到了fork操作对Redis单机内存大小的限制。实际上在Redis的使用中,限制单机内存大小的因素非常之多,下面总结一下在主从复制中,单机内存过大可能造成的影响:

(1)切主:当主节点宕机时,一种常见的容灾策略是将其中一个从节点提升为主节点,并将其他从节点挂载到新的主节点上,此时这些从节点只能进行全量复制;如果Redis单机内存达到10GB,一个从节点的同步时间在几分钟的级别;如果从节点较多,恢复的速度会更慢。如果系统的读负载很高,而这段时间从节点无法提供服务,会对系统造成很大的压力。

(2)从库扩容:如果访问量突然增大,此时希望增加从节点分担读负载,如果数据量过大,从节点同步太慢,难以及时应对访问量的暴增。

(3)缓冲区溢出:(1)和(2)都是从节点可以正常同步的情形(虽然慢),但是如果数据量过大,导致全量复制阶段主节点的复制缓冲区溢出,从而导致复制中断,则主从节点的数据同步会全量复制->复制缓冲区溢出导致复制中断->重连->全量复制->复制缓冲区溢出导致复制中断……的循环。

(4)超时:如果数据量过大,全量复制阶段主节点fork+保存RDB文件耗时过大,从节点长时间接收不到数据触发超时,主从节点的数据同步同样可能陷入全量复制->超时导致复制中断->重连->全量复制->超时导致复制中断……的循环。

此外,主节点单机内存除了绝对量不能太大,其占用主机内存的比例也不应过大:最好只使用50%-65%的内存,留下30%-45%的内存用于执行bgsave命令和创建复制缓冲区等。

7. info Replication

在Redis客户端通过info Replication可以查看与复制相关的状态,对于了解主从节点的当前状态,以及解决出现的问题都会有帮助。

主节点:

img

从节点:

img

对于从节点,上半部分展示的是其作为从节点的状态,从connectd_slaves开始,展示的是其作为潜在的主节点的状态。

info Replication中展示的大部分内容在文章中都已经讲述,这里不再详述。

七、总结

下面回顾一下本文的主要内容:

1、主从复制的作用:宏观的了解主从复制是为了解决什么样的问题,即数据冗余、故障恢复、读负载均衡等。

2、主从复制的操作:即slaveof命令。

3、主从复制的原理:主从复制包括了连接建立阶段、数据同步阶段、命令传播阶段;其中数据同步阶段,有全量复制和部分复制两种数据同步方式;命令传播阶段,主从节点之间有PING和REPLCONF ACK命令互相进行心跳检测。

4、应用中的问题:包括读写分离的问题(数据不一致问题、数据过期问题、故障切换问题等)、复制超时问题、复制中断问题等,然后总结了主从复制相关的配置,其中repl-timeout、client-output-buffer-limit slave等对解决Redis主从复制中出现的问题可能会有帮助。

主从复制虽然解决或缓解了数据冗余、故障恢复、读负载均衡等问题,但其缺陷仍很明显:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制;这些问题的解决,需要哨兵和集群的帮助,我将在后面的文章中介绍,欢迎关注。




主从复制

一、作用

  • slave会通过被复制同步master上面的数据,形成数据副本
  • 当master节点宕机时,slave可以升级为master节点承担写操作。
  • 允许有一主多从,slave可以承担读操作,提高读性能,master承担写操作。即达到读写分离

二、简单性质

  • 一个master可以有多个slave
  • 每个slave只能有一个master
  • 每个slave也可以有自己的多个slave
  • 数据流是单向的,从master到slave

三、创建主从的方式

1.slaveof命令

1
2
#在希望成为slave的节点中执行以下命令
slaveof ${masterIP} ${masterPort}

此过程会异步地将master节点中的数据全量地复制到当前节点中

2.通过配置实现

配置项 含义
salveof ${masterIP} ${masterPort} 设置当前节点作为其他节点的slave节点
slave-read-only yes 设置当前slave节点是只读的,不会执行写操作

3.取消主从的方式

1
2
#在不希望作为slave的节点中执行以下命令
salveof no one

执行完成之后,该节点的数据不会被清除。而是不会再同步master中的数据

4.查看当前节点是否主从

  • info replication

run_id与偏移量

1.run_id

run_id是Redis 服务器的随机标识符,用于 Sentinel 和集群,服务重启后就会改变;

当slave节点复制时发现和之前的 run_id 不同时,将会对数据进行全量同步。

查看runid

1
2
redis-cli -p 6379 info server | grep run
run_id:345dda992e5064bc80e01f96ea90f729b722b2ea

2.偏移量

通过对比主从节点的复制偏移量,可以判断主从节点数据是否一致。

  • 参与复制的主从节点都会维护自身的复制偏移量。主节点(master)在处理完写命令后,会把命令的字节长度做累加记录,统计信息在info replication中的master_repl_offset指标中。
  • 从节点每秒上报自身的复制偏移量给主节点,因此主节点也会保存从节点的复制偏移量
  • 从节点在接收到主节点发送的命令后,也会累加记录自身的偏移量。统计在info replication中的slave_repl_offset指标中

全量复制

一、全量复制流程

1.slave -> master : psync ? -1

  • ? 代表当前slave节点不知道master节点的runid
  • -1代表当前slave节点的偏移量为-1

2.master -> slave : +FULLRESYNC runId offset

  • master通知slave节点需要进行全量复制
  • runId:master发送自身节点的runId给slave节点
  • offset:master发送自身节点的offset给slave节点

3.slave : save masterInfo

  • slave节点保存master节点的相关信息(runId与偏移量)

4.master : bgsave

  • master节点通过bgsave命令进行RDB操作

5.master -> slave : send RDB

  • master将bgsave完的RDB结果发送给slave节点

6.master -> slave : send buffer

  • master在执行写操作时,会将写命令写入repl_back_buffer中
  • 为了维护bgsave过程中执行的写操作命令,并同步给slave,master将期间的buffer发送给slave。

7.slave : flush old data

  • slave节点将之前的数据全部清空

8.load RDB

  • slave节点加载RDB

二、全量复制的开销

  • bgsave时间
  • RDB文件网络传输时间
  • slave节点清空数据时间
  • slave节点加载RDB的时间
  • 可能的AOF重写时间,当加载完RDB之后,如果开启了AOF重写,需要重写AOF,以保证AOF最新

三、全量复制的高版本优化

在redis4.0中,优化了psync,简称psync2,实现了即使redis实例重启的情况下也能实现部分同步

部分复制

一、部分复制流程

1.slave -> master : Connection lost

  • 由于网络抖动等原因,slave对master的网络连接发生中断

2.slave -> master : Connection to master

  • slave重新建立与master节点的连接

3.slave -> master : psync runId offset

  • slave节点发送master节点的runId以及自身的offset

4.master -> slave :CONTINUE

  • 在第③步中,master节点校验offset,在当前buffer的范围中,则将反馈从节点CONTINUE表示部分复制。
  • 如果offset不在当前buffer的范围中,则将反馈从节点FULLRESYNC表示需要全量复制
  • buffer的大小默认为1MB,由repl_back_buffer维护

5.master -> slave : send partial data

  • 发送部分数据给slave节点让slave节点完成部分复制

故障处理

一、slave宕机故障

  • 会影响redis服务的整体读性能,对系统可用性没有影响,将slave节点重新启动并执行slaveof即可。

二、master宕机故障

  • redis将无法执行写请求,只有slave节点能执行读请求,影响了系统的可用性
  • 方法1:
    • 随机找一个节点,执行slaveof no one,使其成为master节点
    • 然后对其他slave节点执行slaveof newMatserIp newMasterPort
  • 方法2:
    • 马上重启master节点,它将会重新成为master

开发与运维中的问题

一、读写分离

  • 含义:master只承担写请求,读请求分摊到slave节点执行
  • 可能遇到的问题
    • 复制数据延迟
      • 当写操作从master同步到slave的时候,会有很短的延迟
      • 当网络原因或者slave阻塞时,会有比较长的延迟
      • 在这种情况下,可以通过配置一个事务中的读写都在主库得已实现
      • 可以通过偏移量对这类问题进行监控
    • 读到过期数据(在v3.2中已经解决)
      • 删除过期数据的策略1:操作key的时候校验该key是否过期,如果已经过期,则删除
      • 删除过期数据的策略2:redis内部有一个定时任务定时检查key有没有过期,如果采样的速度比不上过期数据的产生速度,会导致很多过期数据没有被删除。
      • 在redis集群中,有一个约定,slave节点只能读取数据,而不能操作数据
    • 从节点故障

二、配置不一致

  • maxmemory不一致:可能会丢失数据
    • 例如master配置为4G,从节点配置为2G。
  • 数据结构优化参数(例如hash-max-ziplist-entries):导致内存不一致

三、规避全量复制

  • 第一次全量复制
    • 第一次不可避免
    • 小主节点,低峰处理(夜间)
  • 节点运行ID不匹配
    • 主节点重启(运行ID变化)
    • 可以使用故障转移进行处理,例如哨兵或集群。
  • 复制积压缓冲区不足
    • 如果offset在缓冲区之内,则可以完成部分复制,否则需要全量复制
    • 可以增大复制缓冲区的大小:rel_backlog_size,默认1M,可以提升为10MB

四、规避复制风暴

1.单主节点复制风暴

  • 问题:主节点重启,多从节点复制
  • 解决:更换复制拓扑,由(m-s1,s2,s3)的模式改成(m-s1-s1a,s1b)的模式,可以减轻master的压力

2.单机器复制风暴

  • 如下图:机器宕机后,大量全量复制
  • 解决:主节点分散多机器

image-20190806070642691

image-20190806070849190